<?php

/**
 * Description of BaseModule
 *
 * @author David Dusek
 */
class UserModule extends CMS\AbstractModule {
    public function hookAdminMenu() {
        return array("title" => "adminMenuTitle", 
                     "link" => ":User:User:default",                     
                     "img" => "btn_person.png");
    }
    
    public function hookAdminSubmenu() {
        $submenu = array(
            0 => array(
                "title" => "list",
                "link" => ":User:User:default",
                "icon" => "ui-icon-clipboard",
                "right" => "list"
            ),
            1 => array (
                "title" => "add",
                "link" => ":User:User:add",
                "icon" => "ui-icon-plus",
                "right" => "add"
            ),
            2 => array (
                "title" => "listGroup",
                "link" => ":User:Group:default",
                "icon" => "ui-icon-clipboard",
                "right" => "list"
            ),
            3 => array (
                "title" => "addGroup",
                "link" => ":User:Group:add",
                "icon" => "ui-icon-plus",
                "right" => "add"
            )
        );
        return $submenu;
    }
    
    public function hookUserGetAcl() {
        $acl = array(
            "User" => array(
                "name" => "user_aclTitle",
                "operations" => array(
                    "list" => "user_aclList", 
                    "add" => "user_aclAdd", 
                    "edit" => "user_aclEdit", 
                    "delete" => "user_aclDelete",
                    "detail" => "user_aclDetail"
                ),
            ),
            "Group" => array(
                "name" => "user_aclGroupTitle",
                "operations" => array(
                    "list" => "user_aclGroupList", 
                    "add" => "user_aclGroupAdd", 
                    "edit" => "user_aclGroupEdit", 
                    "delete" => "user_aclGroupDelete"
                ),
            ),
        );
        
        return $acl;
    }
    
    /**
     * Hook, ktery se vola okamzite po nacteni modulu jadrem a v kterem se nactou
     * pozadavky na ACL z jednotlivych modulu a ulozi se do databaze i aplikace. 
     */
    public function hookInitModule() {
        // hook pro ziskani zdroju a operaci pro spravu zdroju
        $acl = $this->core->runHook("hookUserGetAcl");
        if ($acl) {
            $this->setAcl($acl);
        }
    }
    
    /**
     * Vyber nazvu modulu, ktere jsou pro zadaneho uzivatele dostupne tj.
     * ma v nich povolenu alespon jednu akci.
     * @param int $userId
     * @return array Pole obsahuje pod ciselnymi indexy nazvy modulu. Kazdy modul
     * je v poli jen jednou a nazev je vcetne slova Module. 
     */
    public function hookUserGetAllowModules($userId) {
        $user = $this->em->getRepository("\CMS\User\Models\User")->find($userId);
        $opIt = $user->group->operations->getIterator();
        
        $res = array();
        while ($opIt->valid()) {
            $moduleName = $opIt->current()->resource->module;
            if (array_search($moduleName . "Module", $res) === FALSE) {
                $res[] = $moduleName . "Module";
            }
            $opIt->next();
        }
        
        return $res;
    }
    
    public function hookUserGetAllowOperations($userId) {
        $user = $this->em->getRepository("\CMS\User\Models\User")->find($userId);
        $opIt = $user->group->operations->getIterator();
        
        $res = array();
        while ($opIt->valid()) {
            $operation = $opIt->current();          
            $res[] = $operation->resource->module . ":" . $operation->resource->presenter . ":" . $operation->value;
            $opIt->next();
        }
        
        return $res;
    }
    
    /**
     * Nastaveni pravidel pristupu ke zdrojum a ulozeni zdroju a operaci do
     * databaze. Zaroven z databaze odstrani vsechny zdroje a operace, ktere
     * jiz nejsou v sysetemu.
     * @param array $acl 
     */
    private function setAcl($acl) {
        if (!is_array($acl)) {
            return;
        }
        
        if ($this->tryCache($acl)) {
            return;
        }                

        $resourceRep = $this->em->getRepository("\CMS\User\Models\Resource");
        $operationRep = $this->em->getRepository("\CMS\User\Models\Operation");
        
        $moduleList = $resourceRep->getModuleList();
        $existingResources = array();
        $existingOperations = array();
        
        foreach ($acl as $module => $modulInfo) {
            $moduleName = substr($module, 0, -6);
            if (($moduleKey = array_search($moduleName, $moduleList)) !== FALSE) {
                unset($moduleList[$moduleKey]);
            }
            
            $existingResources = $resourceRep->getExistingResources($moduleName);
            
            if (is_array($modulInfo)) {
                foreach($modulInfo as $presenterName => $presenterInfo) {                             
                    $resource = $this->findInResources($existingResources, $presenterName);
                    if (!$resource) {
                        $resource = new CMS\User\Models\Resource();
                        $resource->setTitle($presenterInfo["name"]);
                        $resource->setModule($moduleName);                        
                        $resource->setPresenter($presenterName); 
                        
                        $this->em->persist($resource);
                        $existingOperations = array();
                    } else {
                        $existingOperations = $operationRep->getExistingOperations($resource);
                    }
                                        
                    
                    if (is_array($presenterInfo["operations"])) {
                        foreach ($presenterInfo["operations"] as $operValue => $operTitle) {
                            $operation = $this->findInOperations($existingOperations, $operValue);                                    
                            if (!$operation) {
                                $operation = new \CMS\User\Models\Operation();
                                $operation->setTitle($operTitle);  
                                $operation->setValue($operValue);
                            }
                            
                            $resource->addOperation($operation);
                        }
                    } // end if is_array
                    foreach ($existingOperations as $removeOperation) {
                        $this->em->remove($removeOperation);
                    }
                } // end foereach $moduleInfo                                
            } // end if is_array            
            foreach ($existingResources as $removeResource) {
                $this->em->remove($removeResource);
            }
        }// end foereach $acl          
        
        $resourceRep->removeMissingModules($moduleList);
        $this->em->flush();               
    }
    
    /**
     * Odstraneni vsech zdroju pro dany modul.
     * @param string $moduleName 
     */
    public function hookUserRemoveResourceByModule($moduleName) {
        $this->em->getRepository("\CMS\User\Models\Resource")
                ->removeMissingModules(array($moduleName));
    } 
    
    /**
     * Vytvoreni objektu authorizatoru pro aplikaci. Zaroven probehne inicializace
     * zdroju a roli a jsou nastaveny prava pristupu.
     * @return \Nette\Security\Permission Objekt authorizatoru.
     */
    public function hookUserGetAuthorizator() {
        $groupRep = $this->em->getRepository("\CMS\User\Models\Group");
        $resRep = $this->em->getRepository("\CMS\User\Models\Resource");
                
        $acl = new Nette\Security\Permission;
        
        $resources = $resRep->findAll();
        foreach ($resources as $resource) {
            $acl->addResource($resource->module . ":" . $resource->presenter);
        }
        
        $groups = $groupRep->findAll();
        foreach ($groups as $group) {
            $acl->addRole($group->name);
            
            $opIt = $group->operations->getIterator();
            while ($opIt->valid()) {
                $op = $opIt->current();
                $opResource = $op->resource;
                $acl->allow($group->name, $opResource->module . ":" . $opResource->presenter, $op->value);
                $opIt->next();
            }
        }
        
        return $acl;
    }
    
    /**
     * kontrola zda se ziskany vysledek acl neshoduje s poslednim, ktery se ulozil
     * do cache. Pokud se shoduji je vraceno TRUE jinak FALSe
     * @param array $aclArray
     * @return boolean TRUE pokud je v cachy jinak FALSE.
     */
    private function tryCache($aclArray) {
        $aclJSON = json_encode($aclArray);
        $res = FALSE;
        $filename = __DIR__ . "/cache/acl_cache";
        
        if (!file_exists($filename)) {
            $cache = @fopen($filename, 'w');
            @fclose($cache);
        }        
        
        $content = file_get_contents(__DIR__ . "/cache/acl_cache");

        if ($content == $aclJSON) {
            $res = TRUE;
        } else {
            file_put_contents($filename, $aclJSON);
        }
               
        return $res;
    }
      
    /**
     * Projde pole existujicich zdroju pro modul a pokud najde zdroj pro
     * pozadovany preseneter, tak jej vrati a z pole odstrani. Pokud neuspeje
     * vrati FALSE.
     * @param array $existingResources
     * @param String $presenterName
     * @return \CMS\User\Models\Resource|FALSE
     */
    private function findInResources(&$existingResources, $presenterName) {
        foreach ($existingResources as $key => $resource) {
            if ($resource->presenter == $presenterName) {
                $result = $existingResources[$key];
                unset($existingResources[$key]);
                return $result;
            }
        }
        
        return FALSE;
    }
    
    /**
     * Projde pole existujicich operaci pro presenter a pokud najde operaci
     * s pozadovym jmenem tak ji vrati. Pokud neuspeje, vrati FALSE.
     * @param array $existingOperations
     * @param String $value Jmeno operace.
     * @return \CMS\User\Models\Operation|FALSE
     */
    private function findInOperations(&$existingOperations, $value) {
        foreach ($existingOperations as $key => $operation) {
            if ($operation->value == $value) {
                $result = $existingOperations[$key];
                unset($existingOperations[$key]);
                return $result;
            }
        }
        
        return FALSE;
    }
    
    /**
     * Hook pro pridani zaznamu do logu aktivit uzivatele.
     * @param array $data Pole s informacemi, ktere se maji ulozit do logu.
     * Povinne jsou: user (objekt uzivatele ktery akci provedl), 
     * entity (jmeno entity nad kterou byla zmena provedena) a entityId (id 
     * entity, ktere se pouzije jako parametr funkce find). Nepovinne lze zadat
     * title (nazev akce), description (napriklad jaka data byla nove vlozena)
     * a link (adresa na pro zobrazeni obsahu, ktery byl upravovan).
     */
    public function hookUserAddToLog($data) {
        $logRecord = new CMS\User\Models\UserLog;
        
        if (is_array($data) && isset($data["user"]) && isset($data["entity"]) && isset($data["entityId"])) {
            $logRecord->setUser($data["user"]);
            $logRecord->setEntity($data["entity"]);
            $logRecord->setEntityId($data["entityId"]);
            
            if (isset($data["title"])) $logRecord->setTitle($data["title"]);
            if (isset($data["description"])) $logRecord->setDescription($data["description"]);
            if (isset($data["link"])) $logRecord->setLink($data["link"]);            
            
            $this->em->persist($logRecord);
            $this->em->flush();
        }
    }
    
    /**
     * Hook pro ziskani historie zmen spravovanych objektu systemu pro zadaneho
     * uzivatele.
     * @param int $userId Id uzivatele.
     * @param int $maxRes Maximalni pocet vysledku. Pokud je FALSE, tak vrati
     * vsechny.
     * @return array Pole s historii razene od nejnovejsiho po nejstarsi.
     */
    public function hookUserLogForUser($userId, $maxRes = FALSE) {
        return $this->em->getRepository("\CMS\User\Models\UserLog")
                ->getForUser($userId, $maxRes);
    }
    
    /**
     * Hook pro vyber historie zmen pro pozadovany zdroj podle nazvu entity a 
     * id entity.
     * @param string $entityName Nazev entity.
     * @param int $entityId Id entity.
     * @param int $maxRes Maximalni pocet vysledku. Pokud je FALSE, tak vrati
     * vsechny.
     * @return array Pole s historii razene od nejnovejsiho po nejstarsi.
     */
    public function hookUserLogForContent($entityName, $entityId, $maxRes = FALSE) {
        return $this->em->getRepository("\CMS\User\Models\UserLog")
                ->getForContent($entityName, $entityId, $maxRes = FALSE);
                
    }
    
    /**
     * Vytvoreni komponenty s historii urcite entity.
     * @param string $compName Jmeno komponenty.
     * @param AdminPresenter $presenter Presenter pro ktery se komponeta vytvari.
     * @param string $entityName Nazev entity.
     * @param int $entityId Id entity.
     * @return \Gridito\Grid 
     */
    public function hookUserGetLogComponent($compName, $presenter, $entityName, $entityId) {        
        $dict = $presenter->dictionary;
        $grid = new \Gridito\Grid($presenter, $compName);        
       
        $qb = $this->em->getRepository('\CMS\User\Models\UserLog')
                ->createQueryBuilder('l')
                    ->where("l.entity = '$entityName' AND l.entityId = $entityId")
                    ->orderBy("l.timestamp", "desc");        
        $grid->setModel(new CMS\DoctrineQueryBuilderModel($qb));        
        $grid->addColumn('title', $dict->translate('user_logTitle'), array(
                "renderer" => function ($userLog) use ($dict) {
                    $attrs = array("href" => $userLog->link);
                    $link = \Nette\Utils\Html::el('a')->addAttributes($attrs);
                    $link->add($dict->translate($userLog->title));
                    echo $link;
                }
            ))->setSortable(true);
        $grid->addColumn('user', $dict->translate('user_logUser'), array(
                "renderer"  => function ($userLog) use ($presenter) {
                    $user = $userLog->getUser();
                    $attrs = array("href" => $presenter->link(":User:User:edit", array("id" => $user->id)));
                    $link = \Nette\Utils\Html::el('a')->addAttributes($attrs);
                    $link->add($user->name . " " . $user->surname);
                    echo $link;
                }
            ))->setSortable(true);  
        $grid->addColumn('timestamp', $dict->translate('user_logTimestamp'))->setSortable(true);          
        $grid->addColumn('description', $dict->translate('user_logDescription'), array(
                "renderer"  => function ($userLog) use ($dict) {                    
                    $data = json_decode($userLog->description, true);
                    if ($data !== NULL && is_array($data)) {
                        $table = \Nette\Utils\Html::el('table')
                                        ->setClass("user-logTable");
                        foreach ($data as $key => $value) {
                            $col1 = \Nette\Utils\Html::el('td')->add($dict->translate($key));
                            $col2 = \Nette\Utils\Html::el('td')->add(\Nette\Utils\Strings::truncate($value, 50));                            
                            $table->add(\Nette\Utils\Html::el('tr')->add($col1)->add($col2));
                        }
                        $descCont = $table;
                    } else {
                        $descCont = strlen($userLog->description) > 0 ? $userLog->description : "";
                    }
                       
                    if (strlen ($descCont) > 0) {                                            
                        $divDesc = \Nette\Utils\Html::el('div')->add($descCont)
                                        ->setClass("user-logDiv")
                                        ->setId("logDescription" . $userLog->id);
                        $showA = \Nette\Utils\Html::el('a')
                                ->addAttributes(array(
                                    "id" => "logDescriptionLink" . $userLog->id,
                                    "class" => "user-logLink",
                                    "onclick" => "showDescription(" . $userLog->id . ")"
                                ))
                                ->add($dict->translate("user_logShowDescription") . " >>");

                        echo $showA . $divDesc;
                    }
                }
            ));
        return $grid;
    }
}
